package cn.hackcloud.config.zookeeper;

import cn.hackcloud.config.scope.RefreshScopeRegistry;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.framework.recipes.cache.ChildData;
import org.apache.curator.framework.recipes.cache.PathChildrenCache;
import org.apache.curator.framework.recipes.cache.PathChildrenCacheEvent;
import org.apache.curator.framework.recipes.cache.PathChildrenCacheListener;
import org.apache.curator.retry.ExponentialBackoffRetry;
import org.apache.zookeeper.CreateMode;
import org.apache.zookeeper.data.Stat;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.env.OriginTrackedMapPropertySource;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.core.env.MutablePropertySources;
import org.springframework.core.env.PropertySource;

import javax.annotation.PostConstruct;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;

@ConditionalOnProperty(value = "config.mode", havingValue = "zk", matchIfMissing = true)
@AutoConfigureAfter(RefreshScopeRegistry.class)
public class CuratorUtil {
    private static final Logger logger = LoggerFactory.getLogger(CuratorUtil.class);
    private CuratorFramework client;

    @Value("${zookeeper.client:47.93.251.127:2181}")
    private String connectionStr;

    @Value("${global.config.enable:false}")
    private boolean enable;

    private static String path = "/config";

    private static String ZK_PROPERTY_NAME = "zookeeperSource";

    private volatile Map configMap = new ConcurrentHashMap();

    private static String SCOPE_NAME = "config";

    private static BeanDefinitionRegistry beanDefinitionRegistry;

    @Autowired
    private ConfigurableApplicationContext configurableApplicationContext;

    @PostConstruct
    public void init() {
        if (!enable) return;
        RefreshScopeRegistry refreshScopeRegistry = configurableApplicationContext.getBean(RefreshScopeRegistry.class);
        beanDefinitionRegistry = refreshScopeRegistry.getBeanDefinitionRegistry();

        client = CuratorFrameworkFactory.builder()
                .connectString(connectionStr).sessionTimeoutMs(5000)
                .retryPolicy(new ExponentialBackoffRetry(1000, 3))
                .build();
        client.start();
        try {
            Stat stat = client.checkExists().forPath(path);
            if (stat == null) {
                client.create().creatingParentContainersIfNeeded().withMode(CreateMode.PERSISTENT)
                        .forPath(path, "zk config".getBytes());
                TimeUnit.SECONDS.sleep(1);
            } else {
                //如果节点存在,则要获取节点属性加载到spring容器中
                addChildToStringProperty(client, path);
            }
            childNodeCache(client, path);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            logger.info("global config started");
        }
    }

    private void childNodeCache(CuratorFramework client, String path) throws Exception {
        final PathChildrenCache pathChildrenCache = new PathChildrenCache(client, path, false);
        pathChildrenCache.start(PathChildrenCache.StartMode.POST_INITIALIZED_EVENT);
        pathChildrenCache.getListenable().addListener(new PathChildrenCacheListener() {
            @Override
            public void childEvent(CuratorFramework client, PathChildrenCacheEvent event) throws Exception {
                switch (event.getType()) {
                    case CHILD_ADDED:
                        logger.info("CHILD_ADDED");
                        addEnv(event.getData(), client);
                        break;
                    case CHILD_UPDATED:
                        logger.info("CHILD_UPDATED");
                        addEnv(event.getData(), client);
                        break;
                    case CHILD_REMOVED:
                        logger.info("CHILD_REMOVED");
                        delEnv(event.getData());
                        break;
                }
                refreshBean();
            }
        });
    }

    private void refreshBean() {
        String[] beanDefinitionNames = configurableApplicationContext.getBeanDefinitionNames();
        for (String beanDefinitionName : beanDefinitionNames) {
            BeanDefinition beanDefinition = beanDefinitionRegistry.getBeanDefinition(beanDefinitionName);
            if (SCOPE_NAME.equalsIgnoreCase(beanDefinition.getScope())) {
                //删除bean
                configurableApplicationContext.getBeanFactory().destroyScopedBean(SCOPE_NAME);
                //重新创建就会触发value的依赖注入
                configurableApplicationContext.getBean(beanDefinitionName);
            }
        }
    }

    private void delEnv(ChildData childData) {
        ChildData next = childData;
        String childPath = next.getPath();
        MutablePropertySources propertySources = configurableApplicationContext.getEnvironment().getPropertySources();
        PropertySource<?> zkPropertySource = propertySources.get(ZK_PROPERTY_NAME);
        ConcurrentHashMap zkMap = (ConcurrentHashMap) zkPropertySource.getSource();
        zkMap.remove(childPath.substring(path.length() + 1));
    }

    /**
     * 如果有节点新增、那么就把节点数据加载到环境中
     *
     * @param childData
     * @param client
     */
    private void addEnv(ChildData childData, CuratorFramework client) throws Exception {
        ChildData next = childData;
        String childPath = next.getPath();
        String data = new String(client.getData().forPath(childPath));
        MutablePropertySources propertySources = configurableApplicationContext.getEnvironment().getPropertySources();
        PropertySource<?> zkPropertySource = propertySources.get(ZK_PROPERTY_NAME);
        ConcurrentHashMap zkMap = (ConcurrentHashMap) zkPropertySource.getSource();
        zkMap.put(childPath.substring(path.length() + 1), data);
    }

    private void addChildToStringProperty(CuratorFramework client, String path) {
        if (!checkExistsSpringProperty()) {
            //不存在则去创建配置对象
            createZookeeperSpringProperty();
        }
        MutablePropertySources propertySources = configurableApplicationContext.getEnvironment().getPropertySources();
        PropertySource<?> zkPropertySource = propertySources.get(ZK_PROPERTY_NAME);
        ConcurrentHashMap zkMap = (ConcurrentHashMap) zkPropertySource.getSource();
        try {
            List<String> childPaths = client.getChildren().forPath(path);
            for (String childPath : childPaths) {
                zkMap.put(childPath, new String(client.getData().forPath(path + "/" + childPath)));
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private void createZookeeperSpringProperty() {
        OriginTrackedMapPropertySource zkPropertySource = new OriginTrackedMapPropertySource(ZK_PROPERTY_NAME, configMap);
        MutablePropertySources propertySources = configurableApplicationContext.getEnvironment().getPropertySources();
        propertySources.addLast(zkPropertySource);
    }

    /**
     * 检查配置是否存在
     */
    private boolean checkExistsSpringProperty() {
        MutablePropertySources propertySources = configurableApplicationContext.getEnvironment().getPropertySources();
        for (PropertySource<?> propertySource : propertySources) {
            if (ZK_PROPERTY_NAME.equalsIgnoreCase(propertySource.getName())) {
                return true;
            }
        }
        return false;
    }

}
